package cn.song.freight.service.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.idev.excel.FastExcel;
import cn.song.freight.domain.dto.*;
import cn.song.freight.domain.entity.AreaDO;
import cn.song.freight.domain.entity.PricingAreaMappingDO;
import cn.song.freight.domain.result.PageUtils;
import cn.song.freight.mapper.AreaMapper;
import cn.song.freight.mapper.PricingAreaMappingMapper;
import cn.song.freight.service.AreaService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 区域服务实现类
 * <pre>
 * 该服务实现提供物流系统的区域管理功能，支持区域的增删改查操作，
 * 并提供区域树形结构的构建功能，用于前端展示层级化的区域信息。
 *
 * 核心功能：
 * - 区域信息的添加、删除、更新和查询
 * - 支持多条件组合查询
 * - 提供树形结构的区域数据组织方式
 * - 数据完整性校验和业务规则验证
 *
 * 区域层级说明：
 * 1. 省级区域（level=1）：包含省份编码和名称
 * 2. 市级区域（level=2）：包含城市编码和名称，关联省级区域
 * 3. 区县级区域（level=3）：包含区县编码和名称，关联市级区域
 *
 * @author SongRenShuo
 * @version 3.0.0
 * @since 2025-08-23
 */
@Slf4j
@Service
public class AreaServiceImpl extends ServiceImpl<AreaMapper, AreaDO> implements AreaService {

    @Resource
    private PricingAreaMappingMapper pricingAreaMappingMapper;

    /**
     * 添加新的区域信息
     * <pre>
     * 该方法实现区域信息的添加功能，会对组合字段的唯一性进行校验，
     * 确保同一组合（省份编码+城市编码+区县编码+层级）不会重复添加，保证数据的完整性。
     * 同时根据层级动态校验对应的必填字段。
     *
     * @param addDTO 区域添加数据传输对象，包含区域的基本信息
     * @return 添加结果，true表示添加成功，false表示添加失败
     * @throws IllegalArgumentException 如果数据校验失败或组合字段已存在
     */
    @Override
    public Boolean addArea(AreaAddDTO addDTO) {
        // 根据层级校验必填字段
        addDTO.validateByLevel();

        // 根据组合字段校验是否已存在（省份编码+城市编码+区县编码+层级）
        LambdaQueryWrapper<AreaDO> codeWrapper = Wrappers.<AreaDO>lambdaQuery()
                .eq(AreaDO::getProvinceCode, addDTO.getProvinceCode())
                .eq(AreaDO::getCityCode, addDTO.getCityCode())
                .eq(AreaDO::getDistrictCode, addDTO.getDistrictCode())
                .eq(AreaDO::getLevel, addDTO.getLevel());
        Assert.isTrue(this.count(codeWrapper) == 0, "区域编码组合已存在（省份编码+城市编码+区县编码+层级）");

        // 根据组合字段校验是否已存在（省份名称+城市名称+区县名称+层级）
        LambdaQueryWrapper<AreaDO> nameWrapper = Wrappers.<AreaDO>lambdaQuery()
                .eq(AreaDO::getProvinceName, addDTO.getProvinceName())
                .eq(AreaDO::getCityName, addDTO.getCityName())
                .eq(AreaDO::getDistrictName, addDTO.getDistrictName())
                .eq(AreaDO::getLevel, addDTO.getLevel());
        Assert.isTrue(this.count(nameWrapper) == 0, "区域名称组合已存在（省份名称+城市名称+区县名称+层级）");

        // 将区域数据保存到数据库
        AreaDO areaDO = new AreaDO();
        BeanUtils.copyProperties(addDTO, areaDO);

        return this.save(areaDO);
    }

    /**
     * 删除指定的区域信息
     * <pre>
     * 该方法实现区域信息的删除功能，会先校验区域是否存在，
     * 并检查该区域是否已被运费策略绑定使用，避免误删正在使用的区域数据。
     *
     * @param deleteDTO 区域删除数据传输对象，包含要删除的区域ID
     * @return 删除结果，true表示删除成功，false表示删除失败
     * @throws IllegalArgumentException 如果区域不存在或已被策略绑定使用
     */
    @Override
    public Boolean deleteArea(AreaDeleteDTO deleteDTO) {
        Long areaId = deleteDTO.getAreaId();

        // 判断区域是否存在
        AreaDO areaDO = Assert.notNull(this.getById(areaId), "区域信息不存在");

        // 检查是否有运费策略正在使用该区域
        Assert.isFalse(isAreaUsedInStrategy(areaId), "该区域已被运费策略绑定，无法删除");

        // 执行区域删除操作
        boolean removeById = this.removeById(areaId);
        // if (removeById){
            // 操作前数据，JSON格式存储
            // LogHelper.setBeforeData(JSONUtil.toJsonStr(areaDO));
            // 操作后数据，JSON格式存储
            // LogHelper.setAfterData("");
        // }
        return removeById;
    }

    /**
     * 更新区域信息
     * <pre>
     * 该方法实现区域信息的更新功能，会先校验区域是否存在，
     * 然后根据传入的更新数据对区域信息进行更新操作。
     *
     * @param updateDTO 区域更新数据传输对象，包含要更新的区域ID和新信息
     * @return 更新结果，true表示更新成功，false表示更新失败
     * @throws IllegalArgumentException 如果区域不存在
     */
    @Override
    public Boolean updateArea(AreaUpdateDTO updateDTO) {
        Long areaId = updateDTO.getAreaId();

        // 判断区域是否存在
        AreaDO areaDO = Assert.notNull(this.getById(areaId), "区域信息不存在");

        // 更新区域数据
        AreaDO updateAreaDO = new AreaDO();
        BeanUtils.copyProperties(updateDTO, updateAreaDO);
        updateAreaDO.setId(areaId);
        boolean updateById = this.updateById(updateAreaDO);
        // if (updateById){
            // 操作前数据，JSON格式存储
            // LogHelper.setBeforeData(JSONUtil.toJsonStr(areaDO));
            // 操作后数据，JSON格式存储
            // LogHelper.setAfterData(JSONUtil.toJsonStr(updateAreaDO));
        // }
        return updateById;
    }

    /**
     * 查询区域列表（分页）
     * <pre>
     * 该方法实现区域信息的条件查询功能，支持按编码、名称和层级进行查询，
     * 查询结果按层级和编码进行排序，便于展示和处理。支持分页查询。
     *
     * @param queryDTO 区域查询数据传输对象，包含查询条件和分页参数
     * @return 分页的区域列表
     */
    @Override
    public PageUtils<AreaDO> listAreas(AreaQueryDTO queryDTO) {
        LambdaQueryWrapper<AreaDO> wrapper = getAreaDoLambdaQueryWrapper(queryDTO);

        // 执行分页查询
        Page<AreaDO> result = queryDTO.getPage(AreaDO.class);
        this.page(result, wrapper);

        // 使用 PageUtils 封装分页结果
        return PageUtils.getPageUtils(result);
    }

    /**
     * 查询区域列表（兼容性接口，返回所有数据）
     * <pre>
     * 该方法保持与原有接口的兼容性，返回所有符合条件的数据，不进行分页。
     *
     * @param queryDTO 区域查询数据传输对象，包含查询条件
     * @return 所有符合条件的区域列表
     */
    @Override
    public List<AreaDO> listAreasForCompatibility(AreaQueryDTO queryDTO) {
        LambdaQueryWrapper<AreaDO> wrapper = getAreaDoLambdaQueryWrapper(queryDTO);

        // 直接返回所有符合条件的数据，不进行分页
        return this.list(wrapper);
    }

    /**
     * 查询区域树形结构列表
     * <pre>
     * 该方法实现区域信息的树形结构查询功能，先获取符合条件的区域列表，
     * 然后将其构建成树形结构，便于前端进行层级展示。
     *
     * @param queryDTO 区域查询数据传输对象，包含查询条件
     * @return 树形结构的区域列表
     */
    @Override
    public List<AreaTreeDTO> listAreasTree(AreaQueryDTO queryDTO) {
        // 为了树形结构，需要获取所有数据，不进行分页
        LambdaQueryWrapper<AreaDO> wrapper = getAreaDoLambdaQueryWrapper(queryDTO);

        // 直接查询所有数据用于构建树形结构
        List<AreaDO> areaList = this.list(wrapper);

        // 转换为树形结构
        return buildAreaTree(areaList);
    }


    /**
     * 导出区域数据为Excel文件
     * <pre>
     * 该方法实现区域数据的导出功能，根据查询条件获取区域数据，
     * 然后生成Excel格式的文件数据。支持所有查询条件的过滤。
     * 使用 FastExcel 进行高性能 Excel 文件生成。
     *
     * @param queryDTO 区域查询数据传输对象，包含查询条件
     * @return Excel文件的字节数组
     */
    @SneakyThrows
    @Override
    public byte[] exportAreasToExcel(AreaQueryDTO queryDTO) {
        // 为了导出所有数据，不进行分页限制
        LambdaQueryWrapper<AreaDO> wrapper = getAreaDoLambdaQueryWrapper(queryDTO);

        // 获取所有符合条件的数据
        List<AreaDO> areas = this.list(wrapper);

        // 转换为导出 DTO
        List<AreaExportDTO> exportData = areas.stream().map(area -> {
            AreaExportDTO exportDTO = new AreaExportDTO();
            // 使用对象拷贝简化代码
            BeanUtils.copyProperties(area, exportDTO);
            // 特殊处理需要转换的字段
            exportDTO.setAreaId(area.getId() != null ? area.getId().toString() : "");
            exportDTO.setLevel(getLevelName(area.getLevel()));
            return exportDTO;
        }).collect(Collectors.toList());

        // 使用 FastExcel 生成 Excel 文件
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        // 创建 ExcelWriter
        FastExcel.write(outputStream, AreaExportDTO.class)
                .sheet("区域数据")
                .doWrite(exportData);

        byte[] excelData = outputStream.toByteArray();
        outputStream.close();

        log.info("区域数据导出Excel成功，导出 {} 条数据，文件大小: {} 字节", areas.size(), excelData.length);

        return excelData;
    }

    /**
     * 构建区域树形结构（高性能版）
     * <pre>
     * 该方法将扁平化的区域列表构建成树形结构，按照省-市-区县的层级关系进行组织。
     * 通过精确的映射和单次遍历，实现最优性能。
     *
     * 性能优化点：
     * 1. 使用编码作为键建立精确映射，避免遍历查找
     * 2. 单次遍历完成所有处理操作
     * 3. 预先初始化集合容量，减少扩容开销
     * 4. 避免不必要的对象创建和集合操作
     *
     * @param areaList 区域列表
     * @return 树形结构列表
     */
    private List<AreaTreeDTO> buildAreaTree(List<AreaDO> areaList) {
        // 边界条件检查
        if (areaList == null || areaList.isEmpty()) {
            return new ArrayList<>();
        }

        // 使用编码作为键建立映射，提高查找效率
        Map<String, AreaTreeDTO> provinceMap = new HashMap<>(areaList.size() / 3);
        Map<String, AreaTreeDTO> cityMap = new HashMap<>(areaList.size() / 2);
        Map<String, List<AreaTreeDTO>> cityChildrenMap = new HashMap<>(512);
        Map<String, List<AreaTreeDTO>> provinceChildrenMap = new HashMap<>(64);

        // 转换所有节点并按类型分类
        List<AreaTreeDTO> treeList = new ArrayList<>(areaList.size());
        for (AreaDO areaDO : areaList) {
            AreaTreeDTO treeDTO = new AreaTreeDTO();
            BeanUtils.copyProperties(areaDO, treeDTO);
            treeList.add(treeDTO);

            // 根据层级分类并建立映射
            if (areaDO.getLevel() == 1) {
                provinceMap.put(areaDO.getProvinceCode(), treeDTO);
                provinceChildrenMap.put(areaDO.getProvinceCode(), new ArrayList<>());
            } else if (areaDO.getLevel() == 2) {
                cityMap.put(getCityKey(areaDO), treeDTO);
                cityChildrenMap.put(getCityKey(areaDO), new ArrayList<>());
            }
        }

        // 建立父子关系（单次遍历）
        for (AreaTreeDTO area : treeList) {
            if (area.getLevel() == 3) {
                // 区县添加到市
                String cityKey = getCityKeyForDistrict(area);
                List<AreaTreeDTO> cityChildren = cityChildrenMap.get(cityKey);
                if (cityChildren != null) {
                    cityChildren.add(area);
                }
            } else if (area.getLevel() == 2) {
                // 市添加到省
                List<AreaTreeDTO> provinceChildren = provinceChildrenMap.get(area.getProvinceCode());
                if (provinceChildren != null) {
                    provinceChildren.add(area);
                }
            }
        }

        // 设置子节点（常量时间操作）
        // 设置市的子节点
        for (Map.Entry<String, AreaTreeDTO> entry : cityMap.entrySet()) {
            String cityKey = entry.getKey();
            AreaTreeDTO cityNode = entry.getValue();
            List<AreaTreeDTO> children = cityChildrenMap.get(cityKey);
            if (children != null) {
                cityNode.setChildren(children);
            }
        }

        // 设置省的子节点
        for (Map.Entry<String, AreaTreeDTO> entry : provinceMap.entrySet()) {
            String provinceCode = entry.getKey();
            AreaTreeDTO provinceNode = entry.getValue();
            List<AreaTreeDTO> children = provinceChildrenMap.get(provinceCode);
            if (children != null) {
                provinceNode.setChildren(children);
            }
        }

        return new ArrayList<>(provinceMap.values());
    }


    /**
     * 构建区域树形结构
     *  <pre>
     * 该方法将扁平化的区域列表构建成树形结构，按照省-市-区县的层级关系进行组织。
     * 使用多级Map提升查找性能，先建立映射关系，再逐级构建父子关系。
     *
     * 构建流程：
     * 1. 将区域列表转换为AreaTreeDTO对象列表
     * 2. 按类型分类并建立映射关系（省级、市级）
     * 3. 将区县级区域添加到对应的市级节点下
     * 4. 设置市级节点的子节点
     * 5. 将市级区域添加到对应的省级节点下
     * 6. 设置省级节点的子节点
     *
     * @param areaList 区域列表
     * @deprecated 弃用，请使用新版高性能方法 buildAreaTree(List)
     * @see  #buildAreaTree(List)
     * @return 树形结构列表
     */
    private List<AreaTreeDTO> buildAreaTreeDeprecated(List<AreaDO> areaList) {
        // 转换为 AreaTreeDTO 列表
        List<AreaTreeDTO> treeList = areaList.stream().map(areaDO -> {
            AreaTreeDTO treeDTO = new AreaTreeDTO();
            BeanUtils.copyProperties(areaDO, treeDTO);
            return treeDTO;
        }).collect(Collectors.toList());

        // 构建树形结构
        // 使用Map提升查找性能
        Map<String, AreaTreeDTO> provinceMap = new HashMap<>(64);
        Map<String, AreaTreeDTO> cityMap = new HashMap<>(512);
        Map<String, List<AreaTreeDTO>> cityChildrenMap = new HashMap<>(512);
        Map<String, List<AreaTreeDTO>> provinceChildrenMap = new HashMap<>(64);

        // 先按类型分类并建立映射关系
        for (AreaTreeDTO area : treeList) {
            if (area.getLevel() == 1) {
                provinceMap.put(area.getProvinceCode(), area);
                provinceChildrenMap.put(area.getProvinceCode(), new ArrayList<>());
            } else if (area.getLevel() == 2) {
                cityMap.put(area.getCityCode(), area);
                cityChildrenMap.put(area.getCityCode(), new ArrayList<>());
            }
        }

        // 将区县添加到对应的市
        for (AreaTreeDTO area : treeList) {
            if (area.getLevel() == 3) {
                String cityCode = area.getCityCode();
                if (cityChildrenMap.containsKey(cityCode)) {
                    cityChildrenMap.get(cityCode).add(area);
                }
            }
        }

        // 设置市的子节点
        for (AreaTreeDTO city : cityMap.values()) {
            String cityCode = city.getCityCode();
            if (cityChildrenMap.containsKey(cityCode)) {
                city.setChildren(cityChildrenMap.get(cityCode));
            }
        }

        // 将市添加到对应的省
        for (AreaTreeDTO area : treeList) {
            if (area.getLevel() == 2) {
                String provinceCode = area.getProvinceCode();
                if (provinceChildrenMap.containsKey(provinceCode)) {
                    provinceChildrenMap.get(provinceCode).add(area);
                }
            }
        }

        // 设置省的子节点
        for (AreaTreeDTO province : provinceMap.values()) {
            String provinceCode = province.getProvinceCode();
            if (provinceChildrenMap.containsKey(provinceCode)) {
                province.setChildren(provinceChildrenMap.get(provinceCode));
            }
        }

        return new ArrayList<>(provinceMap.values());
    }

    /**
     * 生成市级节点的唯一键
     * @param areaDO 区域DO
     * @return 市级节点唯一键
     */
    private String getCityKey(AreaDO areaDO) {
        return areaDO.getProvinceCode() + "_" + areaDO.getCityCode();
    }

    /**
     * 生成区县级节点对应市级节点的键
     * @param areaTreeDTO 区域TreeDTO
     * @return 对应市级节点的键
     */
    private String getCityKeyForDistrict(AreaTreeDTO areaTreeDTO) {
        return areaTreeDTO.getProvinceCode() + "_" + areaTreeDTO.getCityCode();
    }

    /**
     * 构建区域查询条件包装器（使用LambdaQueryWrapper提升性能和可维护性）
     *
     * @param queryDTO 查询条件DTO
     * @return Lambda查询包装器
     */
    @SuppressWarnings("unchecked")
    private LambdaQueryWrapper<AreaDO> getAreaDoLambdaQueryWrapper(AreaQueryDTO queryDTO) {
        return Wrappers.<AreaDO>lambdaQuery()
                // 查询编码字段 - 使用eq精确查询
                .eq(StrUtil.isNotBlank(queryDTO.getProvinceCode()), AreaDO::getProvinceCode, queryDTO.getProvinceCode())
                .eq(StrUtil.isNotBlank(queryDTO.getCityCode()), AreaDO::getCityCode, queryDTO.getCityCode())
                .eq(StrUtil.isNotBlank(queryDTO.getDistrictCode()), AreaDO::getDistrictCode, queryDTO.getDistrictCode())
                // 查询名称字段 - 使用like模糊查询
                .like(StrUtil.isNotBlank(queryDTO.getProvinceName()), AreaDO::getProvinceName, queryDTO.getProvinceName())
                .like(StrUtil.isNotBlank(queryDTO.getCityName()), AreaDO::getCityName, queryDTO.getCityName())
                .like(StrUtil.isNotBlank(queryDTO.getDistrictName()), AreaDO::getDistrictName, queryDTO.getDistrictName())
                // 查询层级 - 使用精确匹配
                .eq(queryDTO.getLevel() != null, AreaDO::getLevel, queryDTO.getLevel())
                // 按层级和编码排序，提升查询性能
                .orderByAsc(AreaDO::getLevel, AreaDO::getProvinceCode, AreaDO::getCityCode, AreaDO::getDistrictCode);
    }

    /**
     * 获取层级名称
     *
     * @param level 层级编码
     * @return 层级名称
     */
    private String getLevelName(Integer level) {
        if (level == null) {
            return "未知";
        }
        switch (level) {
            case 1:
                return "省级";
            case 2:
                return "市级";
            case 3:
                return "区县级";
            default:
                return "未知";
        }
    }

    /**
     * 判断区域是否被运费策略使用的方法
     * 通过查询 pricing_area_mapping 表来验证区域是否被策略使用
     *
     * @param areaId 区域ID
     * @return 是否被使用
     */
    private boolean isAreaUsedInStrategy(Long areaId) {
        // 检查参数有效性
        if (areaId == null) {
            return false;
        }

        // 这里通过查询 pricing_area_mapping 表来判断
        // 使用 QueryWrapper 查询是否存在关联记录
        LambdaQueryWrapper<PricingAreaMappingDO> queryWrapper = Wrappers.<PricingAreaMappingDO>lambdaQuery()
                .eq(PricingAreaMappingDO::getAreaId, areaId);

        // 如果存在任何关联记录，则表示该区域正在被使用
        return pricingAreaMappingMapper.selectCount(queryWrapper) > 0;
    }

}
